<?php
/**
 * Request
 *
 * URL请求模式处理
 *
 * @author   songhuan <iphper@yeah.net>
 * @version   $Id: Request.php 1 2010-08-10 14:40:00Z $
 */
class Request
{
	/**
     * Scheme for http
     *
     */
    const SCHEME_HTTP  = 'http';

    /**
     * Scheme for https
     *
     */
    const SCHEME_HTTPS = 'https';
	
	/**
     * @var array url链接样式 http://www.yoursite.com/index.php?s=controller/action
     */
	const NORMAL = 1;
	
	/**
     * @var array url链接样式 http://www.yoursite.com/index.php/controller/action
     */
	const PATHINFO = 2;
	
	/**
     * @var array url链接样式 http://www.yoursite.com/controller/action
     */
	const REWRITE = 3;

	/**
     * @var array url链接样式 
     */
	protected static $url_models = array(
		self::NORMAL => 'NORMAL', 
		self::PATHINFO => 'PATHINFO', 
		self::REWRITE => 'REWRITE'
	);
	
	/**
	 * @var string 
	 */
    protected $_baseUrl = null;
    
    /**
	 * @var string 
	 */
    protected $_requestUri = null;
    
    /**
	 * @var string 
	 */
    protected $_pathInfo = null;
	
	/**
	 * @var string 控制器类名
	 */
    protected $_controller = null;

    /**
	 * @var string 方法名
	 */
    protected $_action = null;
	
	private static $_instance = null;
	
	private function __construct()
    {
    	$url_model = null;
    	if (array_key_exists(V::$config['controller']['url_model'], self::$url_models))
    		$url_model = strtolower(self::$url_models[V::$config['controller']['url_model']]);
    	else if (in_array(strtoupper(V::$config['controller']['url_model']), self::$url_models))
    		$url_model = strtolower(V::$config['controller']['url_model']);
    	
    	if (null === $url_model)
    		throw new Exception('the url model is wrong, must be NORMAL or 1 or PATHINFO or 2 or REWRITE or 3');
    		
		$this->$url_model();
    }
    
    private function __clone() 
    {
    }

	public static function getInstance()
    {
        if (null === self::$_instance) 
            self::$_instance = new self();

        return self::$_instance;
    }
    
	/**
	 * @return string Controller名
	 */
	public function getController()
	{
		return $this->_controller;
	}
	
	/**
	 * @return string Action名
	 */
	public function getAction()
	{
		return $this->_action;
	}
    
    /**
	 * 解析URL，获取Controller和Action
	 * 
	 * @return void
	 */
    public function normal()
    {
    	if (empty($_GET['s'])) 
			$s = empty($_POST['s']) ? '' : $_POST['s'];
		else 
			$s = $_GET['s'];
		
		$s = strtolower(trim(trim($s), '/'));
		
		if (false === strpos($s, '/')) 
			$this->_controller = $s;
		else 
			list($this->_controller, $this->_action) = explode('/', $s);
    }
    
    /**
	 * 解析URL，获取Controller和Action，为_GET赋值
	 * 
	 * @return void
	 */
    public function pathinfo()
    {
    	if (isset($_SERVER['PATH_INFO']))
    		$this->setPathInfo($_SERVER['PATH_INFO']);
    		
    	$this->rewrite();
    }
    
    /**
	 * 解析URL，获取Controller和Action，为_GET赋值
	 * 重写模式，必须开启mod_rewrite，根目录下必须有.htaccess文件
	 * 
	 * @return void
	 */
    public function rewrite()
    {
    	$pathinfo = trim($this->getPathInfo(), '/');
		$bits     = explode('/', $pathinfo);
		
		if (isset($bits[0]))
			$this->_controller = array_shift($bits);
			
		if (isset($bits[0]))
			$this->_action = array_shift($bits);
		
		$last = count($bits);
		if ($last > 0)
		{
			for ($i = 0; $i < $last - 1; $i += 2)
			{
				$_GET[$bits[$i]] = $bits[$i+1];
				$_GET[] = $bits[$i+1];
			}
		}
		
		if ($last % 2 !== 0)
			$_GET[] = array_pop($bits);
    }
    
    /**
     * 获取$this->_pathInfo
     * 
     * @return string $this->_pathInfo
     */
    public function getPathInfo()
    {
    	if (null === $this->_pathInfo)
    		$this->setPathInfo();
    	
    	return $this->_pathInfo;
    }
    
    /**
     * 获取$_SERVER['PATH_INFO']设置全局变量
     * 
     * @return void
     */
	public function setPathInfo($pathInfo = null)
	{
		if (null === $pathInfo) 
        {
			$baseUrl = $this->getBaseUrl();
			
			if (null === ($requestUri = $this->getRequestUri())) 
				return $this;
			
			// Remove the query string from REQUEST_URI
			if ($pos = strpos($requestUri, '?')) 
				$requestUri = substr($requestUri, 0, $pos);
			
			// If substr() returns false then PATH_INFO is set to an empty string
			if ((null !== $baseUrl) && (false === ($pathInfo = substr($requestUri, strlen($baseUrl)))))
				$pathInfo = ''; 
			elseif (null === $baseUrl) 
				$pathInfo = $requestUri;
		}

		$this->_pathInfo = (string) $pathInfo;
		return $this;
	}
    
    /**
     * 获取$this->_baseUrl
     * 
     * @return string $this->_baseUrl
     */
    public function getBaseUrl()
    {
    	if (null === $this->_baseUrl)
    		$this->setBaseUrl();
    	
    	return $this->_baseUrl;
    }
    
    /**
     * 设置全局变量 _baseUrl
     * 
     * @return $this
     */
	public function setBaseUrl($baseUrl = null)
    {
		if ((null !== $baseUrl) && !is_string($baseUrl)) 
			return $this;

		if (null === $baseUrl) 
		{
			$filename = (isset($_SERVER['SCRIPT_FILENAME'])) ? basename($_SERVER['SCRIPT_FILENAME']) : '';
			
			if (isset($_SERVER['SCRIPT_NAME']) && basename($_SERVER['SCRIPT_NAME']) === $filename) 
				$baseUrl = $_SERVER['SCRIPT_NAME'];
			else if (isset($_SERVER['PHP_SELF']) && basename($_SERVER['PHP_SELF']) === $filename)
				$baseUrl = $_SERVER['PHP_SELF'];
			// 1and1 shared hosting compatibility
			else if (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $filename)
				$baseUrl = $_SERVER['ORIG_SCRIPT_NAME']; 
			else 
			{
				// Backtrack up the script_filename to find the portion matching
				// php_self
				$path    = isset($_SERVER['PHP_SELF']) ? $_SERVER['PHP_SELF'] : '';
				$file    = isset($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '';
				$segs    = explode('/', trim($file, '/'));
				$segs    = array_reverse($segs);
				$index   = 0;
				$last    = count($segs);
				$baseUrl = '';
				do 
				{
					$seg     = $segs[$index];
					$baseUrl = '/' . $seg . $baseUrl;
					++$index;
				} 
				while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos));
			}
			
			// Does the baseUrl have anything in common with the request_uri?
			$requestUri = $this->getRequestUri();
			
			if (0 === strpos($requestUri, $baseUrl)) 
			{
				// full $baseUrl matches
				$this->_baseUrl = $baseUrl;
				return $this;
			}
			
			if (0 === strpos($requestUri, dirname($baseUrl))) 
			{
				// directory portion of $baseUrl matches
				$this->_baseUrl = rtrim(dirname($baseUrl), '/');
				return $this;
			}
			
			$basename = basename($baseUrl);
			if (empty($basename) || !strpos($requestUri, $basename)) 
			{
				// no match whatsoever; set it blank
				$this->_baseUrl = '';
				return $this;
			}
			
			// If using mod_rewrite or ISAPI_Rewrite strip the script filename
			// out of baseUrl. $pos !== 0 makes sure it is not matching a value
			// from PATH_INFO or QUERY_STRING
			if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0)))
				$baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl));
		}

		$this->_baseUrl = rtrim($baseUrl, '/');
		return $this;
    }
    
    /**
     * 获取$this->_requestUri
     * 
     * @return string $this->_requestUri
     */
    public function getRequestUri()
    {
    	if (null === $this->_requestUri)
    		$this->setRequestUri();	
    	
    	return $this->_requestUri;
    }
    
    /**
     * 获取$_SERVER['REQUEST_URI']设置全局变量
     * 
     * @return void
     */
	public function setRequestUri($requestUri = null)
	{
		if (null === $requestUri) 
		{
			// check this first so IIS will catch
			if (isset($_SERVER['HTTP_X_REWRITE_URL'])) 
				$requestUri = $_SERVER['HTTP_X_REWRITE_URL'];
			// IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem)
			else if (isset($_SERVER['IIS_WasUrlRewritten']) && $_SERVER['IIS_WasUrlRewritten'] == '1'
				 	 && isset($_SERVER['UNENCODED_URL']) && $_SERVER['UNENCODED_URL'] != '') 
				$requestUri = $_SERVER['UNENCODED_URL'];
			else if (isset($_SERVER['REQUEST_URI'])) 
			{
				$requestUri = $_SERVER['REQUEST_URI'];
				// Http proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path
				$schemeAndHttpHost = $this->getScheme() . '://' . $this->getHttpHost();
				if (strpos($requestUri, $schemeAndHttpHost) === 0) 
					$requestUri = substr($requestUri, strlen($schemeAndHttpHost));
			} 
			// IIS 5.0, PHP as CGI
			else if (isset($_SERVER['ORIG_PATH_INFO'])) 
			{ 
				$requestUri = $_SERVER['ORIG_PATH_INFO'];
				if (!empty($_SERVER['QUERY_STRING'])) 
					$requestUri .= '?' . $_SERVER['QUERY_STRING'];
			} 
			else
				return $this;
		} 
		else if (!is_string($requestUri)) 
			return $this;
		
		$this->_requestUri = $requestUri;
		return $this;
    }
	
	/**
     * 获取HTTP协议
     * 
     * @return string HTTPS or HTTP
     */
	public function getScheme()
	{
		return ($this->getServer('HTTPS') == 'on') ? self::SCHEME_HTTPS : self::SCHEME_HTTP;
	}
	
	/**
     * 获取HTTP_HOST
     * 
     * @return string HTTPS or HTTP
     */
	public function getHttpHost()
    {
        $host = $this->getServer('HTTP_HOST');
        if (!empty($host)) 
            return $host;

        $scheme = $this->getScheme();
        $name   = $this->getServer('SERVER_NAME');
        $port   = $this->getServer('SERVER_PORT');

        if (($scheme == self::SCHEME_HTTP && $port == 80) || ($scheme == self::SCHEME_HTTPS && $port == 443)) 
            return $name;
        else 
            return $name . ':' . $port;
    }
	
    /**
     * 从$_SERVER中取值
     * 
     * @param string $key
     * @param string $default
     * @return string 
     */
    public function getServer($key = null, $default = null)
    {
        if (null === $key) 
            return $_SERVER;
		
        return (isset($_SERVER[$key])) ? $_SERVER[$key] : $default;
    }
}